通过回调来理解Promise

我们都知道Promise的出现是为了规避回调地狱的,由此,我们先来深入了解一下回调的缺陷:

回调

1、缺乏信任

2、不确定性

Example

var money = 30;
order(money,function getOrder(orderId){  // order是一个第三方的下订单的回调函数
   orderId && pay(orderId);  // 获取订单编号之后调用第三方支付pay方法去付款 
})
// ...同步代码

代码解读:上面的代码只是常规代码中的一部分,其中order是一个第三方下订单的方法,需要携带回调函数过去获取订单编号,这里面就存在一些信任性和不确定性的问题,如:

1、有可能回调函数getOrder始终不会被调用,那我们就永远拿不到订单编号去支付;
2、有可能回调了,可是回调了不止一次,导致我们重复去支付了;
3、不确定回掉时间,有可能立马回调,那么接下来的同步代码就会在回调之后执行,有可能它内部调用了异步代码再回调,那么接下来的同步代码就会在回调之前执行。
4、也有可能下订单的第三方模块自己内部出错了,导致我们没办法捕获异常;

上面只是回调的一部分缺陷,但其实当我们不喜欢一个东西的时候总有这么多理由,当我们喜欢一个东西的时候,没有理由也会找一堆理由的,下面就是Promise的理由。

Promise

1、可信任性
2、确定性

在解决上述的回调函数的问题之前,有必要先来认识一下Promise的一些主要方法:

1、Promise的起点new Promise()

Example

console.log(1);
let promise = new Promise(function PromiseBack(resolve,reject){
    resolve();
    resolve();
    reject();
    reject();
    console.log(2);
}).then(()=>{
    console.log(4);
},()=>{
    console.log(5);
})
console.log(3);
// 执行结果依次是:1,2,3,4

代码解读:以上代码体现了new Promise的如下特性:

1、一旦决议(调用过一次resolve或者reject)就不再重复调用决议回调或者改变决议回调。

2、决议代码是同步的,可是决议的成功或失败的回调代码一定是异步的,而且Promise的异步实现比setTimeout的调用时间更早,因为回调决议存在于Event loop的microtask队列中。

2、多Promise同时执行:Promise.all([ .. ])

Example

let promise1 = new Promise(function(resolve,reject){
    resolve(1);
});
let promise2 = new Promise(function(resolve,reject){
    resolve(2);
    // reject(3);
});
let promise3 = Promise.all([promise1,promise2]).then(function resolveBack(result){
    console.log(result);    // 打印结果是:[ 1, 2 ]
},function rejectBack(result){
    console.log(result);    // 当promise2注释部分放开,非注释部分注释,打印结果是:3
})

代码解读:Promise.all方法对于promise数组的决议是:当其中所有的promise都是成功决议的时候,就会调用成功的决议回调resolveBack,并且把promise数组的决议值以数组的形式按照顺序返回resolve中的值;如果其中哪怕有一个失败的决议都会调用失败的决议回调rejectBack,并且只返回promise宿主中失败的决议值,以数组顺序返回

3、多Promise竞赛执行:Promise.race([ .. ])

Example

let promise1 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(1);
  },100);
});
let promise2 = new Promise(function(resolve,reject){
  setTimeout(function(){
   //resolve(2);
    reject(3);
  },50);
});
let promise3 = Promise.race([promise1,promise2]).then(function resolveBack(result){
    console.log(result);    // 打印结果是:2
},function rejectBack(result){
    console.log(result);    // 当promise2注释部分放开,非注释部分注释,打印结果是:3
})

代码解读:Promise.race方法和all方法不同,它对于promise数组的决议是:当promise数组中有一个成功了,那就会立即执行成功的决议回调,当有一个失败了,就会立即执行失败的决议回调,所以,它也叫竞赛决议,它的一个常用的应用场景就是异步请求超时处理,代码如下:
Example:

// request(..)是一个支持Promise的Ajax工具
let promise1 = request( "http://some.url.1/");
let promise2 = function(second=1000){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('请求超时');
    },second)
  })
};
let promise3 = Promise.race([promise1,promise2(3000)])
.then(function resolveBack(result){
    console.log(result);
},function rejectBack(result){
    console.log(result);    // 当请求超时3秒钟就决议失败,打印错误信息,如果这里信息是统一处理,那最好超时的值构造成和异步请求返回错误结果的值一致
})

4、Promise错误处理catch...

Example

new Promise((resolve,reject)=>{
  console.log(a);
}).catch((err)=>{
  console.log(err);     // 打印结果:33 [ReferenceError: a is not defined]
})

代码解读:catch和then一样都是Promise的实例方法,而且也都调用完成之后也都会返回一个新的Promise实例,这样就可以继续then或catch了,这也就是Promise的“链式流”。其实catch本身实现和then是类似的,可以完全看成是then仅有失败决议回调,即:then(null,(err)=>{})。也就是说then的失败决议当Promise决议代码出错了,哪怕没有调用reject方法,也是会被捕获到错误的

总结

浏览过上述方法之后,我们现在来解决一开始我们遇到的那个回调函数的问题,解决问题代码如下:

Example

var money = 30;
let promise1 = new Promise(function(resolve,reject){
    order(money,function getOrder(orderId){ 
        orderId ? resolve(orderId) : reject(orderId);
    }); 
});
let promise2 = function(second=1000){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject('请求超时');
    },second)
  })
};
let promise3 = Promise.race([promise1,promise2(3000)])
.then(function resolveBack(result){
    console.log(result);
},function rejectBack(result){
    console.log(result);    // 当请求超时,或者order内部代码错误等都会调用失败的决议
})
// ...同步代码

代码解读:上面这个Promise例子展示了对回调问题的规避,具体解决思路是:

1、通过使用Promise.race竞赛决议方法解决如果第三方的order方法不调用getOrder的情况,可以定位到具体的错误代码和错误原因;
2、根据Promise的决议特性:一旦决议不可状态不可更改,不可重复。解决了回调了不止一次的问题;
3、根据Promise的决议回调是异步的特性,决议的回调一定是异步的特性,解决了回调时间的不确定性。
4、根据rejectBack类似catch的特性,失败的决议回调是可以捕获到决议代码异常的报错的,那这样,如果第三方内部出现了问题,是可以捕获到的。


夜里的太阳
469 声望16 粉丝

炫耀从来不是我的动机,好奇才是